package dk.itu.mario.level;

import java.util.Arrays;
import java.util.Random;

import dk.itu.mario.MarioInterface.Constraints;
import dk.itu.mario.MarioInterface.GamePlay;
import dk.itu.mario.MarioInterface.LevelInterface;
import dk.itu.mario.engine.sprites.SpriteTemplate;
import dk.itu.mario.engine.sprites.Enemy;

public class MyLevel extends Level {
	//Store information about the level
	public int ENEMIES = 0; //the number of enemies the level contains
	public int BLOCKS_EMPTY = 0; // the number of empty blocks
	public int BLOCKS_COINS = 0; // the number of coin blocks
	public int BLOCKS_POWER = 0; // the number of power blocks
	public int COINS = 0; //These are the coins in boxes that Mario collect

	private static Random levelSeedRandom = new Random();
	public static long lastSeed;

	// Comment

	Random random;

	private int difficulty;
	private int type;
	private int gaps;

	public MyLevel(int width, int height)
	{
		super(width, height);
	}

	public MyLevel(int width, int height, long seed, int difficulty, int type, GamePlay playerMetrics)
	{
		this(width, height);
		creat(seed, difficulty, type, playerMetrics);
	}

	public void creat(long seed, int difficulty, int type, GamePlay playerMetrics)
	{
		this.type = type;
		this.difficulty = difficulty;

		lastSeed = seed;
		random = new Random(seed);

		Classify base = new Classify(playerMetrics, difficulty);
		Classify[] classify = perlinNoise(base);

		//create the start location
		int length = 0;
		length += buildStraight(length, width - length, true);
		length += buildRandom(length, width - length - 16, classify);

		//create all of the medium sections
		while (length < width - 64)
		{
			//length += buildZone(length, width - length);
			length += buildStraight(length, width - length, false);
			length += buildStraight(length, width - length, false);
			length += buildHillStraight(length, width - length);
			length += buildJump(length, width - length);
			length += buildTubes(length, width - length);
			length += buildCannons(length, width - length);
		}

		//set the end piece
		int floor = height - 1 - random.nextInt(4);

		xExit = length + 8;
		yExit = floor;

		// fills the end piece
		for (int x = length; x < width; x++)
		{
			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y, GROUND);
				}
			}
		}

		if (type == LevelInterface.TYPE_CASTLE || type == LevelInterface.TYPE_UNDERGROUND)
		{
			int ceiling = 0;
			int run = 0;
			for (int x = 0; x < width; x++)
			{
				if (run-- <= 0 && x > 4)
				{
					ceiling = random.nextInt(4);
					run = random.nextInt(4) + 4;
				}
				for (int y = 0; y < height; y++)
				{
					if ((x > 4 && y <= ceiling) || x < 1)
					{
						setBlock(x, y, GROUND);
					}
				}
			}
		}

		fixWalls();
		
		countBlocks();

	}

	private void countBlocks() {
		BLOCKS_EMPTY = 0;
		BLOCKS_COINS = 0;
		BLOCKS_POWER = 0;
		for (int x = 0; x < width; x++)
			for (int y = 0; y < height; y++) 
				if (getBlockCapped(x, y) == BLOCK_EMPTY)
					BLOCKS_EMPTY++;
				else if (getBlockCapped(x, y) == BLOCK_COIN)
					BLOCKS_COINS++;
				else if (getBlockCapped(x, y) == BLOCK_POWERUP)
					BLOCKS_POWER++;
	}

	/**
	 * Takes in one classification object and generates individual classifications for each x coordinate in the map
	 * The probabilities of classifications vary evenly between adjacent squares, so the function is smooth
	 * Uses Perlin Noise as the algorithm
	 * @param base Initial Classification
	 * @return Classification array of size width, for each
	 */
	private Classify[] perlinNoise(Classify base) {
		Classify[] classify = new Classify[width];
		double[] prob = base.getProbability();
		double[][] newprob = new double[width][prob.length];
		int step = 8; // Perlin Noise increment: 8 encourages appropriate clustering
		double[][] pivot = new double[width / step + 1][prob.length];
		for (int i = 0; i < pivot.length; i++)
			for (int j = 0; j < prob.length; j++)
				pivot[i][j] = sample(prob[j]);
		for (int i = 0; i < width; i++)
			for (int j = 0; j < prob.length; j++) {
				int i0 = i / step;
				double shift = ((double) (i % step)) / step;
				newprob[i][j] = pivot[i0][j] * smooth(shift) + pivot[i0 + 1][j] * smooth(1 - shift);
			}
		
		for (int i = 0; i < width; i++)
			classify[i] = new Classify();
		for (int i = 0; i < width; i++)
			classify[i].setProbability(newprob[i]);
		return classify;
	}

	/**
	 * Samples a point between 0 and 1 such that the expected value is d
	 * Samples between a < d < b, selecting b with probability r
	 * @param d Expected value
	 * @return Sampled point
	 */
	private double sample(double d) {
		double a = sampleFunc(d), b = 1 - sampleFunc(1 - d);
		double r = (b - d) / (b - a);
		return random.nextDouble() > r ? a : b;
	}

	/**
	 * Function for determining the lower sampled point given d
	 * f(0) = 0
	 * f(1) = 1
	 * r = 1 - d
	 * @param d The input variable
	 * @return The lower sample
	 */
	private double sampleFunc(double d) {
		double a = 3;
		return (a * d * (d - 1) + 1) * d;
	}

	/**
	 * A differential function such that:
	 * f(0) = 1
	 * f(1) = 0
	 * f'(0) = 0
	 * f'(1) = 0
	 * @param a The input variable
	 * @return f(a)
	 */
	private double smooth(double a) {
		return (-2 * a + 3) * a * a;
	}

	/**
	 * Populates the specified segment of the level based on the probabilities contained in c.
	 * @param x0 Starting position
	 * @param x1 Ending position (exclusive)
	 * @param c Probabilities at each position
	 * @return The length of the segment
	 */
	private int buildRandom(int x0, int x1, Classify[] c) {
		buildRandomTerrain(x0, x1, c);
		int[] h = checkHeight(x0, x1);
		boolean[] o = buildRandomSurfaceElements(x0, x1, h, c);
		buildRandomEnemies(x0, x1, h, c);
		buildRandomPlatforms(x0, x1, h, o, c);
		buildCoins(x0, x1, h, c);
		buildRandomBoxes(x0, x1, h, c);
		return x1 - x0;
	}

	/**
	 * Generates land, flat surfaces with periodic discontinuities.
	 * The average length between discontinuities is inversely proportional to complexity, using floor density.  
	 * Then, the type of discontinuity is chosen between a gap and a ridge, depending on jump probability.  
	 * The width of the gap and the hight of ridges are uniformly distributed across what the player is capable of accomplishing.  
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param c The probabilities at each position
	 */
	private void buildRandomTerrain(int x0, int x1, Classify[] c) {
		int h0 = height - 5;
		int floor = 2 * height / 3;
		if (x0 > 0)
			for (int y = 0; y < height; y++)
				if (getBlockCapped(x0 - 1, y) != 0) {
					h0 = y;
					break;
				}
		if (h0 < floor)
			h0 = floor;

		int t = 0;
		for (int x = x0; x < x1; x++) {
			t++;
			// Probability of a gap or ledge increases linearly over time
			if (c[x].floorDensity * (t - 1) > random.nextDouble() && x < x1 - 6) {
				if (c[x].jumpProbability > random.nextDouble()) {
					x += random.nextInt(2) + random.nextInt(2) + random.nextInt(2) + 1;
				} else {
					if (random.nextBoolean())
						h0 += random.nextInt(3) + 1;
					else
						h0 -= random.nextInt(3) + 1;
					if (h0 < floor)
						h0 = floor + random.nextInt(3);
					if (h0 > height - 1)
						h0 = height - 1 - random.nextInt(3);
				}
				t = 0;
			}
			for (int y = height - 1; y >= h0; y--)
				setBlock(x, y, GROUND);
		}
		
//		// Testing area
//		for (int x = x0; x < length; x++)
//			setBlock(x, 2, (byte) (x - x0));
//		for (int x = x0; x < length; x++)
//			setBlock(x, 3, (byte) 4);
//		for (int x = x0; x < length; x += 4)
//			setBlock(x, 4, (byte) GROUND);
//		for (int x = x0; x < length; x += 16)
//			setBlock(x, 5, (byte) GROUND);
//		for (int x = x0; x < length; x += 64)
//			setBlock(x, 6, (byte) GROUND);
	}

	/**
	 * Determines the height at each position as the highest non-empty block
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @return The heights at each position
	 */
	private int[] checkHeight(int x0, int x1) {
		int[] h = new int[x1 - x0];
		for (int x = x0; x < x1; x++) {
			h[x - x0] = height;
			for (int y = 0; y < height; y++)
				if (getBlockCapped(x, y) != 0) {
					h[x - x0] = y;
					break;
				}
		}
		return h;
	}

	/**
	 * Builds surface elements on the available tiles, drawing from the total set of surface elements.  
	 * Parameterizes surface element generation.  
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The current height map
	 * @param c The probabilities at each position
	 * @return The occupancy of the tiles: true if there is now an object on the tile
	 */
	private boolean[] buildRandomSurfaceElements(int x0, int x1, int[] h, Classify[] c) {
		boolean[] o = new boolean[x1 - x0];
		for (int i = 0; i < x1 - x0; i++)
			o[i] = h[i] >= height;
		// Cannon, pipe, stool, log
		byte[][][] b = { { { 14, 30, 46 } }, { { 10, 26 }, { 11, 27 } }, { { 24, 40 } }, { { 25, 41, 57 } } };
		double[][] p = new double[b.length][x1 - x0];
		boolean[] e = new boolean[b.length];
		for (int x = x0; x < x1; x++) {
			int j = 0, i = x - x0;
			Classify c0 = c[x];
			e[j] = true;
			p[j++][i] = c0.cannons;
			e[j] = true;
			p[j++][i] = c0.pipes;
			e[j] = true;
			p[j++][i] = c0.pipes;
			e[j] = false;
			p[j++][i] = c0.logs;
		}

		// Repeat four times to reduce dependance on order
		for (int j = 0; j < 4; j++)
			for (int i = 0; i < b.length; i++)
				buildSurfaceElements(x0, x1, h, o, p[i], b[i], e[i], true);

		return o;
	}

	/**
	 * Parameterizes surface element placement.  
	 * Elements must be below four tiles from their neighbors' heights, and must be above ground.  
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The height map
	 * @param o The tile occupancy
	 * @param p The probability of placement at each position
	 * @param b The encoding of the tile to place
	 * @param e True if the tile extends in height; the last segment is repeated
	 * @param s True if the tile is solid
	 */
	private void buildSurfaceElements(int x0, int x1, int[] h, boolean[] o, double[] p, byte[][] b, boolean e, boolean s) {
		int wx = b.length, wy = b[0].length;
		if (e)
			wy--;
		for (int x = 1; x < x1 - x0 - wx - 1; x++)
			if (p[x] > random.nextDouble()) {
				int h0 = h[x - 1], h1 = h[x + wx], ho = h0 > h1 ? h0 : h1, hi = 0, li = height;
				boolean o0 = false;
				for (int xi = x; xi < x + wx; xi++) {
					if (h[xi] > hi)
						hi = h[xi];
					if (h[xi] < li)
						li = h[xi];
					if (o[xi])
						o0 = true;
				}
				int y = li - wy;
				if (!o0 && ho < height && ho - 5 < y && (e || li == hi)) {
					int y0 = y;
					if (e)
						y0 -= random.nextInt(y - ho + 5);
					buildElement(x + x0, y0, b);
					for (int xi = x; xi < x + wx; xi++) {
						o[xi] = true;
						if (s)
							h[xi] = y0;
					}
				}
			}
	}

	/**
	 * Places a tile at the designated position.  The last segment extends to the ground.  
	 * @param x0 X position
	 * @param y0 Y position
	 * @param b The encoding of the tile to print
	 */
	private void buildElement(int x0, int y0, byte[][] b) {
		int wx = b.length, wy = b[0].length;
		for (int i = 0; i < wx; i++)
			for (int j = 0; j < height - y0; j++) {
				int x = x0 + i, y = y0 + j, j0 = j < wy ? j : wy - 1;
				if (getBlockCapped(x, y) == 0)
					setBlock(x, y, b[i][j0]);
				else
					break;
			}
	}

	/**
	 * Places enemies based on classify.  The enemies are placed along the height map.  
	 * The enemy types are decided by their respective variable; the eccentric types may disrupt game play.  
	 * Flying uses the flying variable for all enemies.  
	 * Parameterizes enemy placement.  
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The height map
	 * @param c The probability at each position
	 */
	private void buildRandomEnemies(int x0, int x1, int[] h, Classify[] c) {
		int n = x1 - x0;
		double[] f = new double[n];
		for (int x = x0; x < x1; x++)
			f[x - x0] = c[x].flying;
		boolean[] o = new boolean[n];
		byte[] b = { 0, 1, 2, 3, 4, 5, 6, 7 };
		double[][] p = new double[b.length][x1 - x0];
		for (int x = x0; x < x1; x++) {
			int j = 0, i = x - x0;
			Classify c0 = c[x];
			p[j++][i] = c0.redTurtle;
			p[j++][i] = c0.greenTurtle;
			p[j++][i] = c0.Goomba;
			p[j++][i] = c0.armoredTurtle;
			p[j++][i] = c0.eccentric * c0.chompFlower;
			p[j++][i] = c0.eccentric * c0.cannonBall;
			p[j++][i] = c0.jumpFlower;
			p[j++][i] = c0.eccentric * c0.invisible;
		}

		for (int j = 0; j < 4; j++)
			for (int i = 0; i < b.length; i++)
				buildEnemies(x0, x1, h, o, b[i], p[i], f);
		buildChompFlowers(x0,x1,h,c);
	}

	/**
	 * Places a chomp flower in tubes with the probability of chomp flowers
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The height map
	 * @param c The probability at each position
	 */
	private void buildChompFlowers(int x0, int x1, int[] h, Classify[] c) {
		for (int x = x0; x < x1; x++) {
			int i = x - x0;
			if (getBlockCapped(x, h[i]) == TUBE_TOP_LEFT) {// && classify[i].chompFlower > random.nextDouble()) {
				setSpriteTemplate(x, h[i], new SpriteTemplate(4, false));
				ENEMIES++;
			}
		}
	}

	/**
	 * Places the specified enemies on valid platforms.  
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The height map
	 * @param o The occupancy map
	 * @param b The enemy encoding
	 * @param p The probability of placement
	 * @param f The probability of flying creatures
	 */
	private void buildEnemies(int x0, int x1, int[] h, boolean[] o, byte b, double[] p, double[] f) {
		for (int x = x0; x < x1; x++) {
			int i = x - x0;
			if (h[i] < height && !o[i] && p[i] > random.nextDouble()) {
				boolean fly = f[i] > random.nextDouble();
				setSpriteTemplate(x, h[i] - 1, new SpriteTemplate(b, fly));
				o[i] = true;
				ENEMIES++;
			}
		}
	}

	/**
	 * Places coins on map where the player can reach.  Avoids gaps
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The height map
	 * @param c The probabilities
	 */
	private void buildCoins(int x0, int x1, int[] h, Classify[] c) {
		int n = x1 - x0;
		int[] h0 = new int[n];
		for (int i = 0; i < n; i++)
			h0[i] = h[i];
		for (int i = 1; i < n; i++)
			if (h0[i] < height && h0[i] > h0[i - 1])
				h0[i] = h0[i - 1] + 1;
		for (int i = n - 2; i >= 0; i--)
			if (h0[i] < height && h0[i] > h0[i + 1])
				h0[i] = h0[i + 1] + 1;

		for (int x = x0; x < x1; x++) {
			int i = x - x0;
			if (h0[i] < height) {
				for (int y = h[i]; y >= h0[i] - 4; y--) {
					if (getBlockCapped(x, y) == COIN) {
						setBlock(x, y, (byte) 0);
						COINS--;
					}
					if (getBlockCapped(x, y) == 0 && c[i].coins > random.nextDouble()) {
						setBlock(x, y, COIN);
						COINS++;
					}
				}
			}
		}
	}

	/**
	 * Builds all of the types of platforms.  Parameterizes platform placement
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The height map
	 * @param o The occupancy map
	 * @param c The probabilities
	 */
	private void buildRandomPlatforms(int x0, int x1, int[] h, boolean[] o, Classify[] c) {
		byte[][][] b = { { { -124, -108 }, { -123, -107 }, { -122, -106 } }, { { -32, -16 }, { -31, -15 }, { -30, -14 } } };
		byte[][][] b0 = { { { -124, -76 }, { -122, -74 } }, null };
		double[][] p = new double[b.length][x1 - x0];
		for (int x = x0; x < x1; x++) {
			int j = 0, i = x - x0;
			p[j++][i] = c[x].hills;
			p[j++][i] = c[x].stools;
		}

		// Repeat many times to ensure stacking
		for (int j = 0; j < 8; j++)
			for (int i = 0; i < b.length; i++)
				buildPlatforms(x0, x1, h, o, p[i], b[i], b0[i], c);
	}

	/**
	 * Places the specified type of platform, extending or stretching if needed.  
	 * Platforms need to be reachable at some point but off the ground.  
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The height map
	 * @param o The occupancy map
	 * @param p The probability of placement
	 * @param b The platform encoding
	 * @param b0 The platform replacement; allows stacking and replaces corners
	 * @param c The probabilities, for enemy placement
	 */
	private void buildPlatforms(int x0, int x1, int[] h, boolean[] o, double[] p, byte[][] b, byte[][] b0, Classify[] c) {
		int n = x1 - x0;
		boolean[] o0 = new boolean[n];
		for (int i = 0; i < n; i++) {
			o0[i] = o[i];
			byte q = getBlockCapped(x0, h[i]);
			if (b0 != null)
				for (int j = 0; j < b.length; j++)
					if (q == b[j][0])
						o0[i] = false;
		}

		for (int x = x0; x < x1; x++) {
			int i = x - x0;
			int w = 2 + random.nextInt(4);
			if (w + i <= n && p[i] > random.nextDouble()) {
				int y0 = height;
				boolean ok = false;
				for (int j = 0; j < w; j++) {
					int hk = h[i + j];
					if (hk < y0)
						y0 = hk;
					if (o0[i + j])
						ok = true;
				}

				int y = y0 - 1 - random.nextInt(4);
				if (!ok && y > 0) {
					if (b0 != null)
						for (int xk = x; xk < x + w; xk++)
							for (int yk = y; yk < height; yk++)
								for (int j = 0; j < b0.length; j++)
									if (b0[j][0] == getBlockCapped(xk, yk))
										setBlock(xk, yk, b0[j][1]);
					buildPlatform(x, x + w, y, b);

					int[] h0 = new int[w];
					for (int j = 0; j < w; j++) {
						o[i + j] = true;
						if (b0 == null)
							o0[i + j] = true;
						h[i + j] = y;
						h0[j] = y;
					}
					buildRandomEnemies(x, x + w, h0, c);
				}
			}
		}
	}

	/**
	 * Builds the platform at the specified position, stretching it if necessary.  
	 * @param x0 The start tile
	 * @param x1 The end tile
	 * @param y0 The top of the platform
	 * @param b The platform encoding
	 */
	private void buildPlatform(int x0, int x1, int y0, byte[][] b) {
		int wx = b.length - 2;
		for (int x = x0; x < x0 + wx; x++)
			buildPlatformRow(x, y0, b[x - x0]);
		for (int x = x0 + wx; x < x1 - 1; x++)
			buildPlatformRow(x, y0, b[wx]);
		buildPlatformRow(x1 - 1, y0, b[wx + 1]);
	}

	/**
	 * Builds a component of the platform at the designated position 
	 * @param x The position
	 * @param y0 The top of the platform
	 * @param b The platform column encoding
	 */
	private void buildPlatformRow(int x, int y0, byte[] b) {
		int wy = b.length - 1;
		for (int y = y0; y < height; y++) {
			int j = y - y0;
			if (j > wy)
				j = wy;
			if (getBlockCapped(x, y) == 0)
				setBlock(x, y, b[j]);
			else 
				break;
		}
	}

	/**
	 * Parameterizes the building of boxes.  
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The height map
	 * @param c The probabilities
	 */
	private void buildRandomBoxes(int x0, int x1, int[] h, Classify[] c) {
		byte[][] b0 = { { 16, 17, 18, 19 } }; // Only one type so far
		byte[][] b1 = { { 20, 21, 22, 23 } };
		double[][] p = new double[b0.length][x1 - x0];
		double[][] p0 = new double[b0.length][x1 - x0];
		for (int x = x0; x < x1; x++) {
			int j = 0, i = x - x0;
			Classify c0 = c[x];
			p[j][i] = c0.blocks;
			p0[j++][i] = c0.powerBlock;
		}

		for (int j = 0; j < 8; j++)
			for (int i = 0; i < b0.length; i++)
				buildBoxes(x0, x1, h, b0[i], b1[i], p[i], p0[i], c);
	}

	/**
	 * Builds boxes of the selected types.  
	 * Boxes must be three or more units from all ground, but reachable on one side.  
	 * @param x0 The initial position
	 * @param x1 The final position
	 * @param h The height maps
	 * @param b0 The first type encodings, selected uniformly randomly
	 * @param b1 The second type encodings, chosen with probability, selected uniformly randomly
	 * @param p The probability of placement
	 * @param p0 The probability of the second box set
	 * @param c The probabilities, for enemy placement
	 */
	private void buildBoxes(int x0, int x1, int[] h, byte[] b0, byte[] b1, double[] p, double[] p0, Classify[] c) {
		int n = x1 - x0;
		for (int x = x0 + 1; x < x1; x++) {
			int i = x - x0;
			int w = 1 + random.nextInt(4);
			if (i + w < n - 1 && p[i] > random.nextDouble()) {
				int y0 = height, y1 = height;
				for (int j = -1; j < w + 1; j++) {
					int hk = h[i + j];
					if (hk < y0)
						y0 = hk;
				}

				for (int j = 0; j < w; j++) {
					int hk = h[i + j];
					if (hk < y0)
						y0 = hk;
				}

				for (int j = -4; j < 0; j++)
					if (i + j >= 0) {
						int hk = h[i + j] - j;
						if (hk < y1)
							y1 = hk;
					}
				for (int j = w; j < w + 4; j++)
					if (i + j < n) {
						int hk = h[i + j] + j - w;
						if (hk < y1)
							y1 = hk;
					}

				if (y0 >= y1) {
					int y = y1 - 3 + random.nextInt(y0 - y1 + 1);
					if (y > 0) {
						int[] h0 = new int[w];
						for (int xk = x; xk < x + w; xk++) {
							int ik = xk - x0;
							byte[] bk = p0[ik] < random.nextDouble() ? b0 : b1;
							setBlock(xk, y, bk[random.nextInt(bk.length)]);
							h[ik] = y;
							h0[xk - x] = h[ik];
						}
						buildRandomEnemies(x, x + w, h0, c);

						int[] h1 = new int[w + 8];
						for (int xk = x - 4; xk < x + w + 4; xk++) {
							int ik = xk - (x - 4);
							if (xk >= x0 && xk < x1)
								h1[ik] = h[xk - x0];
							else
								h1[ik] = height;
						}
						buildCoins(x - 4, x + w + 4, h1, c);
					}
				}
			}
		}
	}

	private int buildJump(int xo, int maxLength)
	{
		gaps++;
		//jl: jump length
		//js: the number of blocks that are available at either side for free
		int js = random.nextInt(4) + 6;
		int jl = random.nextInt(2) + 2;
		int length = js * 2 + jl;

		boolean hasStairs = .25 > random.nextDouble();
		boolean cannon = .125 > random.nextDouble();

		int floor = height - 1 - random.nextInt(4);
		//run from the start x position, for the whole length
		for (int x = xo; x < xo + length; x++)
		{
			if (x < xo + js || x > xo + length - js - 1)
			{
				//run for all y's since we need to paint blocks upward
				for (int y = 0; y < height; y++)
				{ //paint ground up until the floor
					if (y >= floor)
					{
						setBlock(x, y, GROUND);
					}
					//if it is above ground, start making stairs of rocks
					else if (cannon)
					{
						int cannonHeight;
						if (x < xo + js)
						{
							cannonHeight = floor - (x - xo) + 1;
						}
						else
						{
							cannonHeight = floor - ((xo + length) - x) + 2;
						}
						if (y == cannonHeight)
						{
							setBlock(x, y, (byte) (14 + 0 * 16));
						}
						else if (y == cannonHeight + 1)
						{
							setBlock(x, y, (byte) (14 + 1 * 16));
						}
						else if (y > cannonHeight + 1)
						{
							setBlock(x, y, (byte) (14 + 2 * 16));
						}
					}
					else if (hasStairs)
					{ //LEFT SIDE
						if (x < xo + js)
						{ //we need to max it out and level because it wont
							//paint ground correctly unless two bricks are side by side
							if (y >= floor - (x - xo) + 1)
							{
								setBlock(x, y, ROCK);
							}
						}
						else
						{ //RIGHT SIDE
							if (y >= floor - ((xo + length) - x) + 2)
							{
								setBlock(x, y, ROCK);
							}
						}
					}

				}
			}
		}

		return length;
	}

	private int buildCannons(int xo, int maxLength)
	{
		int length = random.nextInt(10) + 2;
		if (length > maxLength)
			length = maxLength;

		int floor = height - 1 - random.nextInt(4);
		int xCannon = xo + 1 + random.nextInt(4);
		for (int x = xo; x < xo + length; x++)
		{
			if (x > xCannon)
			{
				xCannon += 2 + random.nextInt(4);
			}
			if (xCannon == xo + length - 1)
				xCannon += 10;
			int cannonHeight = floor - random.nextInt(4) - 1;

			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y, GROUND);
				}
				else
				{
					if (x == xCannon && y >= cannonHeight)
					{
						if (y == cannonHeight)
						{
							setBlock(x, y, (byte) (14 + 0 * 16));
						}
						else if (y == cannonHeight + 1)
						{
							setBlock(x, y, (byte) (14 + 1 * 16));
						}
						else
						{
							setBlock(x, y, (byte) (14 + 2 * 16));
						}
					}
				}
			}
		}

		return length;
	}

	private int buildHillStraight(int xo, int maxLength)
	{
		int length = random.nextInt(10) + 10;
		if (length > maxLength)
			length = maxLength;

		int floor = height - 1 - random.nextInt(4);
		for (int x = xo; x < xo + length; x++)
		{
			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y, GROUND);
				}
			}
		}

		addEnemyLine(xo + 1, xo + length - 1, floor - 1);

		int h = floor;

		boolean keepGoing = true;

		boolean[] occupied = new boolean[length];
		while (keepGoing)
		{
			h = h - 2 - random.nextInt(3);

			if (h <= 0)
			{
				keepGoing = false;
			}
			else
			{
				int l = random.nextInt(5) + 3;
				int xxo = random.nextInt(length - l - 2) + xo + 1;

				if (occupied[xxo - xo] || occupied[xxo - xo + l] || occupied[xxo - xo - 1] || occupied[xxo - xo + l + 1])
				{
					keepGoing = false;
				}
				else
				{
					occupied[xxo - xo] = true;
					occupied[xxo - xo + l] = true;
					addEnemyLine(xxo, xxo + l, h - 1);
					if (random.nextInt(4) == 0)
					{
						decorate(xxo - 1, xxo + l + 1, h);
						keepGoing = false;
					}
					for (int x = xxo; x < xxo + l; x++)
					{
						for (int y = h; y < floor; y++)
						{
							int xx = 5;
							if (x == xxo)
								xx = 4;
							if (x == xxo + l - 1)
								xx = 6;
							int yy = 9;
							if (y == h)
								yy = 8;

							if (getBlock(x, y) == 0)
							{
								setBlock(x, y, (byte) (xx + yy * 16));
							}
							else
							{
								if (getBlock(x, y) == HILL_TOP_LEFT)
									setBlock(x, y, HILL_TOP_LEFT_IN);
								if (getBlock(x, y) == HILL_TOP_RIGHT)
									setBlock(x, y, HILL_TOP_RIGHT_IN);
							}
						}
					}
				}
			}
		}

		return length;
	}

	private void addEnemyLine(int x0, int x1, int y)
	{
		for (int x = x0; x < x1; x++)
		{
			if (random.nextInt(35) < difficulty + 1)
			{
				int type = random.nextInt(4);

				if (difficulty < 1)
				{
					type = Enemy.ENEMY_GOOMBA;
				}
				else if (difficulty < 3)
				{
					type = random.nextInt(3);
				}

				setSpriteTemplate(x, y, new SpriteTemplate(type, random.nextInt(35) < difficulty));
				ENEMIES++;
			}
		}
	}

	private int buildTubes(int xo, int maxLength)
	{
		int length = random.nextInt(10) + 5;
		if (length > maxLength)
			length = maxLength;

		int floor = height - 1 - random.nextInt(4);
		int xTube = xo + 1 + random.nextInt(4);
		int tubeHeight = floor - random.nextInt(2) - 2;
		for (int x = xo; x < xo + length; x++)
		{
			if (x > xTube + 1)
			{
				xTube += 3 + random.nextInt(4);
				tubeHeight = floor - random.nextInt(2) - 2;
			}
			if (xTube >= xo + length - 2)
				xTube += 10;

			if (x == xTube && random.nextInt(11) < difficulty + 1)
			{
				setSpriteTemplate(x, tubeHeight, new SpriteTemplate(Enemy.ENEMY_FLOWER, false));
				ENEMIES++;
			}

			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y, GROUND);

				}
				else
				{
					if ((x == xTube || x == xTube + 1) && y >= tubeHeight)
					{
						int xPic = 10 + x - xTube;

						if (y == tubeHeight)
						{
							//tube top
							setBlock(x, y, (byte) (xPic + 0 * 16));
						}
						else
						{
							//tube side
							setBlock(x, y, (byte) (xPic + 1 * 16));
						}
					}
				}
			}
		}

		return length;
	}

	private int buildStraight(int xo, int maxLength, boolean safe)
	{
		int length = random.nextInt(10) + 2;

		if (safe)
			length = 10 + random.nextInt(5);

		if (length > maxLength)
			length = maxLength;

		int floor = height - 1 - random.nextInt(4);

		//runs from the specified x position to the length of the segment
		for (int x = xo; x < xo + length; x++)
		{
			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y, GROUND);
				}
			}
		}

		if (!safe)
		{
			if (length > 5 && .5 > random.nextDouble())
			{
				decorate(xo, xo + length, floor);
				if (.4 > random.nextDouble()) {
					decorate(xo, xo + length, floor - 4);
				}
			}
		}

		return length;
	}

	private void decorate(int xStart, int xLength, int floor)
	{
		//if its at the very top, just return
		if (floor < 1)
			return;

		//        boolean coins = random.nextInt(3) == 0;
		boolean rocks = true;

		//add an enemy line above the box
		addEnemyLine(xStart + 1, xLength - 1, floor - 1);

		int s = random.nextInt(4);
		int e = random.nextInt(4);

		if (floor - 2 > 0) {
			if ((xLength - 1 - e) - (xStart + 1 + s) > 1) {
				for (int x = xStart + 1 + s; x < xLength - 1 - e; x++) {
					setBlock(x, floor - 2, COIN);
					COINS++;
				}
			}
		}

		s = random.nextInt(4);
		e = random.nextInt(4);

		//this fills the set of blocks and the hidden objects inside them
		if (floor - 4 > 0)
		{
			if ((xLength - 1 - e) - (xStart + 1 + s) > 2)
			{
				for (int x = xStart + 1 + s; x < xLength - 1 - e; x++)
				{
					if (rocks)
					{
						if (x != xStart + 1 && x != xLength - 2 && random.nextInt(3) == 0)
						{
							if (random.nextInt(4) == 0)
							{
								setBlock(x, floor - 4, BLOCK_POWERUP);
								BLOCKS_POWER++;
							}
							else
							{ //the fills a block with a hidden coin
								setBlock(x, floor - 4, BLOCK_COIN);
								BLOCKS_COINS++;
							}
						}
						else if (random.nextInt(4) == 0)
						{
							if (random.nextInt(4) == 0)
							{
								setBlock(x, floor - 4, (byte) (2 + 1 * 16));
							}
							else
							{
								setBlock(x, floor - 4, (byte) (1 + 1 * 16));
							}
						}
						else
						{
							setBlock(x, floor - 4, BLOCK_EMPTY);
							BLOCKS_EMPTY++;
						}
					}
				}
			}
		}
	}

	private void fixWalls()
	{
		boolean[][] blockMap = new boolean[width + 1][height + 1];

		for (int x = 0; x < width + 1; x++)
		{
			for (int y = 0; y < height + 1; y++)
			{
				int blocks = 0;
				for (int xx = x - 1; xx < x + 1; xx++)
				{
					for (int yy = y - 1; yy < y + 1; yy++)
					{
						if (getBlockCapped(xx, yy) == GROUND) {
							blocks++;
						}
					}
				}
				blockMap[x][y] = blocks == 4;
			}
		}
		blockify(this, blockMap, width + 1, height + 1);
	}

	private void blockify(Level level, boolean[][] blocks, int width, int height) {
		int to = 0;
		if (type == LevelInterface.TYPE_CASTLE)
		{
			to = 4 * 2;
		}
		else if (type == LevelInterface.TYPE_UNDERGROUND)
		{
			to = 4 * 3;
		}

		boolean[][] b = new boolean[2][2];

		for (int x = 0; x < width; x++)
		{
			for (int y = 0; y < height; y++)
			{
				for (int xx = x; xx <= x + 1; xx++)
				{
					for (int yy = y; yy <= y + 1; yy++)
					{
						int _xx = xx;
						int _yy = yy;
						if (_xx < 0)
							_xx = 0;
						if (_yy < 0)
							_yy = 0;
						if (_xx > width - 1)
							_xx = width - 1;
						if (_yy > height - 1)
							_yy = height - 1;
						b[xx - x][yy - y] = blocks[_xx][_yy];
					}
				}

				if (b[0][0] == b[1][0] && b[0][1] == b[1][1])
				{
					if (b[0][0] == b[0][1])
					{
						if (b[0][0])
						{
							level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
						}
						else
						{
							// KEEP OLD BLOCK!
						}
					}
					else
					{
						if (b[0][0])
						{
							//down grass top?
							level.setBlock(x, y, (byte) (1 + 10 * 16 + to));
						}
						else
						{
							//up grass top
							level.setBlock(x, y, (byte) (1 + 8 * 16 + to));
						}
					}
				}
				else if (b[0][0] == b[0][1] && b[1][0] == b[1][1])
				{
					if (b[0][0])
					{
						//right grass top
						level.setBlock(x, y, (byte) (2 + 9 * 16 + to));
					}
					else
					{
						//left grass top
						level.setBlock(x, y, (byte) (0 + 9 * 16 + to));
					}
				}
				else if (b[0][0] == b[1][1] && b[0][1] == b[1][0])
				{
					level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
				}
				else if (b[0][0] == b[1][0])
				{
					if (b[0][0])
					{
						if (b[0][1])
						{
							level.setBlock(x, y, (byte) (3 + 10 * 16 + to));
						}
						else
						{
							level.setBlock(x, y, (byte) (3 + 11 * 16 + to));
						}
					}
					else
					{
						if (b[0][1])
						{
							//right up grass top
							level.setBlock(x, y, (byte) (2 + 8 * 16 + to));
						}
						else
						{
							//left up grass top
							level.setBlock(x, y, (byte) (0 + 8 * 16 + to));
						}
					}
				}
				else if (b[0][1] == b[1][1])
				{
					if (b[0][1])
					{
						if (b[0][0])
						{
							//left pocket grass
							level.setBlock(x, y, (byte) (3 + 9 * 16 + to));
						}
						else
						{
							//right pocket grass
							level.setBlock(x, y, (byte) (3 + 8 * 16 + to));
						}
					}
					else
					{
						if (b[0][0])
						{
							level.setBlock(x, y, (byte) (2 + 10 * 16 + to));
						}
						else
						{
							level.setBlock(x, y, (byte) (0 + 10 * 16 + to));
						}
					}
				}
				else
				{
					level.setBlock(x, y, (byte) (0 + 1 * 16 + to));
				}
			}
		}
	}

	public RandomLevel clone() throws CloneNotSupportedException {

		RandomLevel clone = new RandomLevel(width, height);

		clone.xExit = xExit;
		clone.yExit = yExit;
		byte[][] map = getMap();
		SpriteTemplate[][] st = getSpriteTemplate();

		for (int i = 0; i < map.length; i++)
			for (int j = 0; j < map[i].length; j++) {
				clone.setBlock(i, j, map[i][j]);
				clone.setSpriteTemplate(i, j, st[i][j]);
			}
		clone.BLOCKS_COINS = BLOCKS_COINS;
		clone.BLOCKS_EMPTY = BLOCKS_EMPTY;
		clone.BLOCKS_POWER = BLOCKS_POWER;
		clone.ENEMIES = ENEMIES;
		clone.COINS = COINS;

		return clone;

	}

}
